﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Globalization;

namespace SynoManager
{
    /// <summary>
    /// Struct that helps in displaying a number of bytes in a human readable format (for example: 1024 bytes will be displayed as 1KB)
    /// </summary>
    public struct ByteSize : IComparable<ByteSize>
    {
        /// <summary>
        /// Threshold at which a 'higher' notation will be used
        /// </summary>
        /// <remarks>767 bytes will be displayed as 767 B, 768B will be displayed as 0,75 KB</remarks>
        const int THRESHOLD = 768;
        readonly string[] UNITS; // holds the unit names

        long bytes;
        int decimals;

        /// <summary>
        /// Initializes a new instance of the <see cref="ByteSize"/> struct.
        /// </summary>
        /// <param name="bytes">The number of bytes.</param>
        public ByteSize(long bytes)
            : this(bytes, 1)
        {
            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ByteSize"/> struct.
        /// </summary>
        /// <param name="bytes">The number bytes.</param>
        /// <param name="decimals">The number of decimals to display.</param>
        public ByteSize(long bytes, int decimals)
        {
            this.bytes = bytes;
            this.decimals = decimals;
            UNITS = new string[] { "B", "KB", "MB", "GB", "TB", "PB" };
        }

        public ByteSize(string text)
        {
            UNITS = new string[] { "B", "KB", "MB", "GB", "TB", "PB" };

            if (text == "NA")
            {
                bytes = -1;
                decimals = 0;
                return;
            }

            text = text.Trim();

            // TODO: calculate this correctly
            text = text.Replace("i", ""); // strips the i out out MiB and KiB. 
            text = text.Replace("Bytes", "B");

            int space = text.IndexOf(' ');
            string intStr = text.Substring(0, space).Trim();
            string unitStr = text.Substring(space + 1).Trim();

            int dot = intStr.IndexOf('.');
            decimals = (dot > 0) ? intStr.Length - dot - 1 : 0;
            double value = double.Parse(intStr, CultureInfo.InvariantCulture);

            int factor = -1;
            for(int i=0;i<UNITS.Length;i++)
            {
                if (String.Equals(UNITS[i], unitStr, StringComparison.InvariantCultureIgnoreCase))
                {
                    factor = i;
                    break;
                }
            }

            Debug.Assert(factor >= 0, "Unknown unit '" + unitStr + "' in '" + text + "'");

            this.bytes = (long)(value * Math.Pow(1024, factor)); // iets met 1024 hier doen
        }


        /// <summary>
        /// Gets or sets the total bytes.
        /// </summary>
        /// <value>The total bytes.</value>
        public long TotalBytes
        {
            get { return bytes; }
            set { bytes = value; }
        }

        /// <summary>
        /// Returns the bytevalue as a readable string
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> containing the byte size in a human readable format
        /// </returns>
        public override string ToString()
        {
            return ToString(decimals);
        }

        /// <summary>
        /// Returns the byte value as a readable string
        /// </summary>
        /// <param name="decimals">Number of decimals to display.</param>
        /// <returns>
        /// A <see cref="T:System.String"/> containing the byte size in a human readable format
        /// </returns>
        public string ToString(int decimals)
        {
            if (decimals == 0)
                return ToString("0");
            else
                return ToString("0." + new String('0', decimals));
        }

        /// <summary>
        /// Returns the byte value as a readable string
        /// </summary>
        /// <param name="format">String formatting to use for the numerical part.</param>
        /// <returns>
        /// A <see cref="T:System.String"/> containing the byte size in a human readable format
        /// </returns>
        public string ToString(string format)
        {
            int i = 0;
            decimal value = bytes;
            while (i < UNITS.Length)
            {
                if (value < THRESHOLD)
                    break;

                i++;
                value = value / 1024;
            }
            return String.Format("{0:" + format + "} {1}", value, UNITS[i]);
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="ds1">The first bytesize.</param>
        /// <param name="ds2">The second bytesize.</param>
        /// <returns>The result of the operator.</returns>
        public static ByteSize operator +(ByteSize ds1, ByteSize ds2)
        {
            return new ByteSize(ds1.bytes + ds2.bytes, ds1.decimals);
        }

        public static long Parse(string sizeString)
        {
            ByteSize size = new ByteSize(sizeString);
            return size.TotalBytes;
        }

        #region IComparable<ByteSize> Members

        public int CompareTo(ByteSize other)
        {
            return bytes.CompareTo(other.bytes);
        }

        #endregion
    }

    /// <summary>
    /// Static Extension class that appends the <see cref="ToByteString(int)" /> to the <see cref="int"/> and <see cref="long"/> types.
    /// </summary>
    public static class ByteSizeExtensions
    {
        /// <summary>
        /// Displays a value (number of bytes) as a human readable string.
        /// </summary>
        /// <param name="bytes">The number bytes.</param>
        /// <returns>
        /// A <see cref="T:System.String"/> containing the byte size in a human readable format
        /// </returns>
        public static string ToByteString(this int bytes)
        {
            return new ByteSize(bytes).ToString();
        }

        /// <summary>
        /// Displays a value (number of bytes) as a human readable string.
        /// </summary>
        /// <param name="bytes">The number bytes.</param>
        /// <returns>
        /// A <see cref="T:System.String"/> containing the byte size in a human readable format
        /// </returns>
        public static string ToByteString(this long bytes)
        {
            return new ByteSize(bytes).ToString();
        }
    }
}
